﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Threading;

namespace Hypermarket_Serial_Port
{
    class Program
    {
        private enum POLLING_STATE {Reload, CashierRegister, PriceTag};
        private const int FIRST_POSITION = 0;
        private const int EMPTY_LIST = 0;
        private const int MAX_POLLING_TIME = 10000;
        private const int TIMER_INTERVAL = 200;

        private const string MSG_COM_PORT = "Please Key the COM Port Name: ";
        private const string ERROR_INIT_VAL = "Initialization Error!\nPress Any key to terminate";
        private const string ERROR_INIT_TIMER = "Init Timer Error!\n";
        private const string ERROR_LOAD_ACTIVATED_MACHINE = "Load Actived Machine Error!\n";
        private const string ERROR_UNEXPECTED = "Unexpected Error!!!\n";

        private const string MSG_TIMER_CALLING_TIME = "The Elapsed event was raised at {0}\n";
        private const string MSG_RELOAD_DATA = "Reload Data\n";
        private const string MSG_POLLING_CASHIER_REGISTER = "Polling Cashier Register\n";
        private const string MSG_POLLING_PRICE_TAG = "Polling Price Tag\n";

        private const string MSG_POLLING_CASHIER_REGISTER_ID = "CashierRegister ID: {0} {1}\n";
        private const string MSG_POLLING_PRICE_TAG_ID = "Price Tag ID: {0} {1}\n";
        private const string MSG_BROADCASTING = "1.Broadcasting...\n";
        private const string ERROR_BROADCASTING = "Broadcasting Error!!!\n";
        private const string MSG_RECEIVING_DATA = "2.Receiving Data...\n";
        private const string ERROR_RECEIVE_DATA = "Receive Data Error!!!\n";
        private const string MAS_REPLYING_DATA = "3.Replying Data...\n";
        private const string ERROR_REPLY_DATA = "Reply Data Error!!!\n";
        private const string MSG_RECEIVING_ACK = "4.Receiving ACK...\n";
        private const string ERROR_RECEIVE_ACK = "4.Receive ACK Error!!!\n";

        private const string ADD_DEACTIVATED_CASHIER_REGISTER = "Add Deactivated CashierRegister: {0}\n";
        private const string ADD_DEACTIVATED_PRICE_TAG = "Add Deactivated PriceTag: {0}\n";

        private const string DEACTIVATED_CASHIER_REGISTER = "Deactiving Cashier Register: {0}\n";
        private const string DEACTIVATED_PRICE_TAG = "Deactiving Price Tag: {0}\n";

        #region Local Variable
        private static CashierRegisterController mCRContorller;
        private static PriceTagController mPTController;
        private static CommuncationManager mCManager;
        private static ActivationManager mAManager;
        private static ProtocolManager mPManager;

        private static List<string> mActivedCashierRegisters;
        private static List<string> mActivedPriceTags;

        private static Dictionary<string, int> mActivedCashierRegisterDict;
        private static Dictionary<string, int> mActivedPriceTagDict;

        private static List<string> mDeactiveCashierRegisterList;
        private static List<string> mDeactivePriceTagList;

        private static int mActivedPosition;
        private static POLLING_STATE mPollingState;
        private static System.Timers.Timer mTimer;
        private static Int32 mPollingRound;
        private static System.Threading.Thread mThread;

        #endregion

        static void Main(string[] args)
        {
            try
            {
                InitVariables();
                InitTimer();
                
                while (true)
                {
                    //ReloadData();
//                    TestCase();
                    //Console.ReadLine();
                //    //ReloadData();
                //    //string s = Console.ReadLine();
                //    //ReloadData();
                //    //string s = Console.ReadLine();
                //    //byte[] inputArr = HexStringToByteArray(s);
                //    //byte[] output = Operation(inputArr, MACHINE.PriceTag);
                //    //s = ByteArrayToHexString(output);
                //    //Console.WriteLine(s);
                }
            }
            catch
            {
                Console.WriteLine(ERROR_INIT_VAL);
                Console.Read();
            }
        }

        private static void TestCase()
        {
            ReloadData();
            List<string> CommandList = new List<string>(System.IO.File.ReadAllLines("TestFile.txt"));
            foreach (string cm in CommandList)
            {
                try
                {
                    Console.Write("Enter: {0}\n", cm);
                    string s = cm;
                    byte[] inputArr = HexStringToByteArray(s);
                    byte[] output = Operation(inputArr, MACHINE.CashierRegister);
                    s = ByteArrayToHexString(output);
                    Console.WriteLine(s);
                }
                catch
                {
                    Console.WriteLine("Unexpected Error!");
                }
            }
        }

        private static void InitVariables()
        {
            Console.Write(MSG_COM_PORT);
            string PortName = Console.ReadLine();
            mCRContorller = CashierRegisterController.getInstance();
            mPTController = PriceTagController.getInstance();
            mAManager = ActivationManager.getInstance();
            mPManager = ProtocolManager.getInstance();
            mCManager = CommuncationManager.getInstance(PortName);

            mActivedCashierRegisters = new List<string>();
            mActivedPriceTags = new List<string>();

            mActivedCashierRegisterDict = new Dictionary<string, int>();
            mActivedPriceTagDict = new Dictionary<string, int>();

            mDeactiveCashierRegisterList = new List<string>();
            mDeactivePriceTagList = new List<string>();

            mActivedPosition = FIRST_POSITION;
            mPollingRound = FIRST_POSITION;
            mPollingState = POLLING_STATE.Reload;
            mTimer = new System.Timers.Timer();
        }

        private static void InitTimer()
        {
            // Set the Interval to 0.1 seconds (100 milliseconds).
            mTimer = new System.Timers.Timer(TIMER_INTERVAL);

            // Hook up the Elapsed event for the timer.
            mTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnTimedEvent);
            mTimer.Enabled = true;
        }

        private static void LoadActivedMachine()
        {
            mActivedCashierRegisters = mAManager.GetActivedCashierRegistorList(mActivedCashierRegisterDict);
            mActivedPriceTags = mAManager.GetActivedPriceTagList(mActivedPriceTagDict);
        }

        //Timer event, handle the polling
        private static void OnTimedEvent(object source, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                switch (mPollingState)
                {
                    case POLLING_STATE.Reload:
                    default:
                        Console.WriteLine("Polling Round: {0}\n", mPollingRound);
                        SerialPortLog.Log("Polling Round");
                        SerialPortLog.Log(mPollingRound.ToString());
                        Console.WriteLine(MSG_RELOAD_DATA);
                        if (mThread == null || !mThread.IsAlive)
                        {
                            mPollingRound++;
                            mThread = new System.Threading.Thread(new System.Threading.ThreadStart(ReloadData));
                            mThread.SetApartmentState(System.Threading.ApartmentState.STA);
                            mThread.Start();
                        }
                        //mTimer.Enabled = false;
                        //ReloadData();
                        //mTimer.Enabled = true;
                        break;
                    case POLLING_STATE.CashierRegister:
                        Console.WriteLine(MSG_POLLING_CASHIER_REGISTER);
                        if (mThread == null || !mThread.IsAlive)
                        {
                            mThread = new System.Threading.Thread(new System.Threading.ThreadStart(CashierRegisterPolling));
                            mThread.SetApartmentState(System.Threading.ApartmentState.STA);
                            mThread.Start();
                        }
//                        CashierRegisterPolling();
                        break;

                    case POLLING_STATE.PriceTag:
                        Console.WriteLine(MSG_POLLING_PRICE_TAG);
                        if (mThread == null || !mThread.IsAlive)
                        {
                            mThread = new System.Threading.Thread(new System.Threading.ThreadStart(PriceTagPolling));
                            mThread.SetApartmentState(System.Threading.ApartmentState.STA);
                            mThread.Start();
                        }
//                        PriceTagPolling();
                        break;                 
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                //switch (mPollingState)
                //{
                //    case POLLING_STATE.Reload:
                //        mPollingState = POLLING_STATE.CashierRegister;
                //        break;
                //    case POLLING_STATE.CashierRegister:
                //        UpdataCashierRegisterPollingState();
                //        break;
                //    case POLLING_STATE.PriceTag:
                //        UpdatePriceTagPollingState();
                //        break;
                //}
            }
        }

        private static void ReloadData()
        {
            foreach (string id in mDeactiveCashierRegisterList)
            {
                Console.WriteLine(DEACTIVATED_CASHIER_REGISTER, id);
                mActivedCashierRegisterDict.Remove(id);
                mActivedCashierRegisters.Remove(id);
                mCRContorller.DeactiveCashierRegister(id);
            }
            foreach (string id in mDeactivePriceTagList)
            {
                Console.WriteLine(DEACTIVATED_PRICE_TAG , id);
                mActivedPriceTagDict.Remove(id);
                mActivedPriceTags.Remove(id);
            }
            mAManager.ReloadData();
            mAManager.Deactive(mDeactiveCashierRegisterList, mDeactivePriceTagList);
            LoadActivedMachine();
            mPollingState = POLLING_STATE.CashierRegister;
        }

        private static void CashierRegisterPolling()
        {
//            SerialPortLog.Log("Cashier Register Thread Start");
            if (mActivedCashierRegisters.Count() == EMPTY_LIST)
            {
                UpdataCashierRegisterPollingState();
//                SerialPortLog.Log("Cashier Register Thread End");
                return;
            }
            Console.WriteLine("Cashier Register List Size: {0}, Position: {1}\n", mActivedCashierRegisters.Count(), mActivedPosition);
            string machineId = mActivedCashierRegisters[mActivedPosition];
            Console.WriteLine(MSG_POLLING_CASHIER_REGISTER_ID, machineId, mActivedCashierRegisterDict[machineId]);
            byte[] data = mPManager.GenerateBroadcast(machineId, MACHINE.CashierRegister);
            
            //1: Broadcast
            //Fail to send Broadcast
            Console.WriteLine(MSG_BROADCASTING);
            if (!mCManager.SerialPortSend(data))
            {
            //    Console.WriteLine(ERROR_BROADCASTING);
                AddDeactiveCashierRegister(machineId);
                UpdataCashierRegisterPollingState();
//                SerialPortLog.Log("Cashier Register Thread End");
                return;
            }

            //2:Receive Data
            //TODO wait for reply...
            Console.WriteLine(MSG_RECEIVING_DATA);
            if (!mCManager.SerialPortReceive(out data) || !mPManager.CheckMachine(ref data, machineId, MACHINE.CashierRegister))
            {
            //    Console.WriteLine(ERROR_RECEIVE_DATA);
                AddDeactiveCashierRegister(machineId);
                UpdataCashierRegisterPollingState();
//                SerialPortLog.Log("Cashier Register Thread End");
                return;
            }

            //Check if End of Communication
            if (mPManager.CheckEndOfCommunication(data))
            {
                UpdataCashierRegisterPollingState();
//                SerialPortLog.Log("Cashier Register Thread End");
                return;
            }

            if (mPManager.CheckCallManager(data))
            {
                mCRContorller.DeactiveCashierRegister(machineId);
                UpdataCashierRegisterPollingState();
                SerialPortLog.Log("Call Manager");
                return;
            }

            data = Operation(data, MACHINE.CashierRegister);
            //invalid data
            if (data.Count() == EMPTY_LIST)
            {
                UpdataCashierRegisterPollingState();
//                SerialPortLog.Log("Cashier Register Thread End");
                return;
            }

            //3:Reply
            Console.WriteLine(MAS_REPLYING_DATA);
            if (!mCManager.SerialPortSend(data))
            {
            //    Console.WriteLine(ERROR_REPLY_DATA);
                AddDeactiveCashierRegister(machineId);
                UpdataCashierRegisterPollingState();
//                SerialPortLog.Log("Cashier Register Thread End");
                return;
            }

            //4:Receive Ack
            Console.WriteLine(MSG_RECEIVING_ACK);
            if (!mCManager.SerialPortReceive(out data) || !mPManager.CheckMachine(ref data, machineId, MACHINE.CashierRegister) || !mPManager.CheckEndOfCommunication(data))
            {
                Console.WriteLine(ERROR_RECEIVE_ACK);
                AddDeactiveCashierRegister(machineId);
            }
            RefleshCashierRegisterMissTime(machineId);
            UpdataCashierRegisterPollingState();
 //           SerialPortLog.Log("Cashier Register Thread End");
        }

        private static void AddDeactiveCashierRegister(string machineId)
        {
            try
            {
                mActivedCashierRegisterDict[machineId]++;
                if (mActivedCashierRegisterDict[machineId] >= MAX_POLLING_TIME)
                {
                    Console.WriteLine(ADD_DEACTIVATED_CASHIER_REGISTER, machineId);
                    mDeactiveCashierRegisterList.Add(machineId);
                }
            }
            catch
            {
                throw;
            }
        }

        private static void RefleshCashierRegisterMissTime(string machineId)
        {
            mActivedCashierRegisterDict[machineId] = FIRST_POSITION;
        }

        private static void UpdataCashierRegisterPollingState()
        {
            try
            {
                mActivedPosition++;
                if (mActivedPosition >= mActivedCashierRegisters.Count())
                {
                    mPollingState = POLLING_STATE.PriceTag;
                    mActivedPosition = FIRST_POSITION;
                }
            }
            catch
            {
                throw;
            }
        }

        private static void PriceTagPolling()
        {
            if (mActivedPriceTags.Count() == EMPTY_LIST)
            {
                UpdatePriceTagPollingState();
                return;
            }
            Console.WriteLine("Price Tag List Size: {0}, Position: {1}\n", mActivedPriceTags.Count(), mActivedPosition);
            string machineId = mActivedPriceTags[mActivedPosition];
            Console.WriteLine(MSG_POLLING_PRICE_TAG_ID, machineId, mActivedPriceTagDict[machineId]);
            byte[] data = mPManager.GenerateBroadcast(machineId, MACHINE.PriceTag);

            //1: Broadcast
            //Fail to send Broadcast
            if (!mCManager.SerialPortSend(data))
            {
                AddDeactivePriceTag(machineId);
                UpdatePriceTagPollingState();
                return;
            }

            //2:Receive Data
            if (!mCManager.SerialPortReceive(out data) || !mPManager.CheckMachine(ref data, machineId, MACHINE.PriceTag))
            {
                AddDeactivePriceTag(machineId);
                UpdatePriceTagPollingState();
                return;
            }

            if (!mPManager.CheckExistance(data))
            {
                UpdatePriceTagPollingState();
                return;
            }
            data = Operation(data, MACHINE.PriceTag);
            if (data.Count() == EMPTY_LIST)
            {
                UpdatePriceTagPollingState();
                return;
            }

            //3:Reply
            if (!mCManager.SerialPortSend(data))
            {
                AddDeactivePriceTag(machineId);
                UpdatePriceTagPollingState();
                return;
            }

            //4:Receive Ack
            if (!mCManager.SerialPortReceive(out data) ||
                !mPManager.CheckMachine(ref data, machineId, MACHINE.PriceTag) || 
                !mPManager.CheckEndOfCommunication(data))
            {
                AddDeactivePriceTag(machineId);
            }
            RefleshPriceTagMissTime(machineId);
            UpdatePriceTagPollingState();
        }

        private static void AddDeactivePriceTag(string machineId)
        {
            try
            {
                mActivedPriceTagDict[machineId]++;
                if (mActivedPriceTagDict[machineId] >= MAX_POLLING_TIME)
                {
                    Console.WriteLine(ADD_DEACTIVATED_PRICE_TAG, machineId);
                    mDeactivePriceTagList.Add(machineId);
                }
            }
            catch
            {
                throw;
            }
        }

        private static void RefleshPriceTagMissTime(string machineId)
        {
            mActivedPriceTagDict[machineId] = FIRST_POSITION;
        }

        private static void UpdatePriceTagPollingState()
        {
            try
            {
                mActivedPosition++;
                if (mActivedPosition >= mActivedPriceTags.Count())
                {
                    mPollingState = POLLING_STATE.Reload;
                    mActivedPosition = FIRST_POSITION;
                }
            }
            catch
            {
                throw;
            }
        }

        private static byte[] Operation(byte[] input, MACHINE machineType)
        {
            byte[] output = new byte[] { };
            try
            {
                string outputStr = string.Empty;
                List<string> InfoList = mPManager.ConvertHexMessageToString(input);
                if (InfoList.Count() == EMPTY_LIST)
                    return output;
                switch (machineType)
                {
                    case MACHINE.CashierRegister:
                        InfoList = mCRContorller.Operation(InfoList);
                        break;

                    case MACHINE.PriceTag:
                        InfoList = mPTController.Operation(InfoList);
                        break;
                }
                output = mPManager.ConvertStringMessageToHex(InfoList);
                return output;
            }
            catch
            {
                SerialPortLog.Log("Operation Fail");
                return output;
            }
        }

        private static string ByteArrayToHexString(byte[] data)
        {
            StringBuilder sb = new StringBuilder(data.Length * 3);
            try
            {
                foreach (byte b in data)
                {
                    sb.Append(Convert.ToString(b, 16).PadLeft(2, '0').PadRight(3, ' '));
                }
                return sb.ToString().ToUpper();
            }
            catch
            {
                return sb.ToString().ToUpper();
            }
        }

        private static byte[] HexStringToByteArray(string s)
        {
            byte[] buffer = new byte[] { };
            try
            {
                s = s.Replace(" ", "");
                buffer = new byte[s.Length / 2];
                for (int i = 0; i < s.Length; i += 2)
                {
                    buffer[i / 2] = (byte)Convert.ToByte(s.Substring(i, 2), 16);
                }
                return buffer;
            }
            catch
            {
                return buffer;
            }
        }
    }
}
